package com.springboot;

import com.springboot.config.CustomApplicationContextInitializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurationImportSelector;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.StopWatch;

import java.util.Iterator;

/**
 * 此 demo 演示 SpringBoot 的简单使用，同时展示各组件的执行顺序(看得更清楚)
 * Spring Boot 自动配置是通过一系列类、接口以及 SPI 机制共同实现的
 *
 * @author yexi
 * @see SpringFactoriesLoader
 * @see SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader) 根据 META-INF/spring.factories 文件中定义的自动配置类来加载相应的配置，然后根据配置条件进行自动化配置。
 * @see AutoConfigurationImportSelector 自动配置的核心入口。它通过 Spring 提供的 SPI（Service Provider Interface）机制，从 spring.factories 文件中找到 EnableAutoConfiguration 配置，并对其进行解析、筛选和排序，最终返回一个 String[] 数组，这个数组包含需要自动配置的 class 名称。
 * @see org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader 负责加载 Spring Boot 自动配置的元数据信息。这个元数据信息包括自动配置类的条件、实现类以及要加载的 bean 等信息，以及关于这些元数据如何设计、打包和加载的详细说明。
 */
@SpringBootApplication
@EnableScheduling
@Slf4j
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = null;
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 开启SpringBoot容器的方式(TIPS:这些方法每调用一次都会额外开启一个容器，这并不符合我们想要Spring做的事情，所以在实际中你只需要使用其中一种即可)
        int startMethod = 1;
        switch (startMethod) {
            case 1:
                System.out.println("--------------方式1(新建Spring上下文对象)--------------");
                SpringApplication springApplication = new SpringApplication(Application.class);
                springApplication.addInitializers(new CustomApplicationContextInitializer());
                context = springApplication.run(args);
                break;
            case 2:
                System.out.println("--------------方式2(静态方法直接运行)(最常用，但扩展性不佳)--------------");
                context = SpringApplication.run(Application.class, args);
                break;
            case 3:
                System.out.println("--------------方式3(通过创建者模式创建)--------------");
                SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(Application.class);
                context = springApplicationBuilder
                        .initializers(new CustomApplicationContextInitializer())
                        .logStartupInfo(false)
                        .run(args);
                break;
            default:
                throw new RuntimeException();
        }

        stopWatch.stop();
        log.info("服务已启动，启动耗时：{}秒\n", stopWatch.getTotalTimeSeconds());


        System.out.println("--------------当前SpringBoot项目成功注册的所有Bean:--------------");
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        for (Iterator<String> beanNamesIterator = beanFactory.getBeanNamesIterator(); beanNamesIterator.hasNext(); ) {
            String beanName = beanNamesIterator.next();
            System.out.println(beanName);
        }

    }

}
